/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "gtest/gtest.h"
#include "test_helper.h"

#include "include/runtime.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/js_handle.h"
#include "plugins/ecmascript/runtime/tagged_array-inl.h"
#include "plugins/ecmascript/runtime/object_factory.h"

using namespace ark::ecmascript;
using namespace ark::coretypes;

namespace ark::test {
class WeakRefGenGCTest : public testing::Test {
public:
    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(instance, thread, scope, "stw");  // issue #5368
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(instance, scope);
    }

    PandaVM *instance {nullptr};
    EcmaHandleScope *scope {nullptr};
    JSThread *thread;
};

#if !defined(NDEBUG)
static JSObject *JSObjectTestCreate(JSThread *thread)
{
    [[maybe_unused]] ecmascript::EcmaHandleScope scope(thread);
    EcmaVM *ecma_vm = thread->GetEcmaVM();
    JSHandle<GlobalEnv> global_env = ecma_vm->GetGlobalEnv();
    JSFunction *js_func = global_env->GetObjectFunction().GetObject<JSFunction>();
    JSHandle<JSTaggedValue> js_func1(thread, js_func);
    JSHandle<JSObject> new_obj =
        ecma_vm->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(js_func1), js_func1);
    return *new_obj;
}
#endif

#if !defined(NDEBUG)
static TaggedArray *ArrayTestCreate(JSThread *thread)
{
    [[maybe_unused]] ecmascript::EcmaHandleScope scope(thread);
    JSHandle<TaggedArray> array = thread->GetEcmaVM()->GetFactory()->NewTaggedArray(2);
    return *array;
}
#endif

TEST_F(WeakRefGenGCTest, DISABLED_ArrayNonMovable)  // issue #5368
{
#if !defined(NDEBUG)
    auto vm = thread->GetEcmaVM();
    auto array = vm->GetFactory()->NewTaggedArray(2, JSTaggedValue::Undefined(), true);
    JSHandle<JSObject> new_obj1(thread, JSObjectTestCreate(thread));
    array->Set(thread, 0, new_obj1.GetTaggedValue());

    JSObject *new_obj2 = JSObjectTestCreate(thread);
    JSTaggedValue value(new_obj2);
    value.CreateWeakRef();
    array->Set(thread, 1, value);
    EXPECT_EQ(new_obj1.GetTaggedValue(), array->Get(0));
    EXPECT_EQ(value, array->Get(1));
    vm->GetGC()->WaitForGC(GCTask(GCTaskCause::EXPLICIT_CAUSE));
    EXPECT_EQ(new_obj1.GetTaggedValue(), array->Get(0));
    EXPECT_EQ(JSTaggedValue::Undefined(), array->Get(1));
#endif
}

TEST_F(WeakRefGenGCTest, DISABLED_ArrayUndefined)
{
#if !defined(NDEBUG)
    EcmaVM *ecma_vm = thread->GetEcmaVM();
    JSHandle<TaggedArray> array = ecma_vm->GetFactory()->NewTaggedArray(2);
    EXPECT_TRUE(*array != nullptr);
    JSHandle<JSObject> new_obj1(thread, JSObjectTestCreate(thread));
    array->Set(thread, 0, new_obj1.GetTaggedValue());

    JSObject *new_obj2 = JSObjectTestCreate(thread);
    JSTaggedValue value(new_obj2);
    value.CreateWeakRef();
    array->Set(thread, 1, value);
    EXPECT_EQ(new_obj1.GetTaggedValue(), array->Get(0));
    EXPECT_EQ(value, array->Get(1));
    ecma_vm->GetGC()->WaitForGC(GCTask(GCTaskCause::EXPLICIT_CAUSE));
    EXPECT_EQ(new_obj1.GetTaggedValue(), array->Get(0));
    EXPECT_EQ(JSTaggedValue::Undefined(), array->Get(1));
#endif
}

TEST_F(WeakRefGenGCTest, DISABLED_ArrayKeep)
{
#if !defined(NDEBUG)
    EcmaVM *ecma_vm = thread->GetEcmaVM();
    JSHandle<TaggedArray> array = ecma_vm->GetFactory()->NewTaggedArray(2);
    EXPECT_TRUE(*array != nullptr);
    JSHandle<JSObject> new_obj1(thread, JSObjectTestCreate(thread));
    array->Set(thread, 0, new_obj1.GetTaggedValue());

    JSHandle<JSObject> new_obj2(thread, JSObjectTestCreate(thread));
    JSTaggedValue value(new_obj2.GetTaggedValue());
    value.CreateWeakRef();
    array->Set(thread, 1, value);
    EXPECT_EQ(new_obj1.GetTaggedValue(), array->Get(0));
    EXPECT_EQ(value, array->Get(1));
    ecma_vm->GetGC()->WaitForGC(GCTask(GCTaskCause::EXPLICIT_CAUSE));
    EXPECT_EQ(new_obj1.GetTaggedValue(), array->Get(0));
    EXPECT_EQ(true, array->Get(1).IsWeak());
    value = new_obj2.GetTaggedValue();
    value.CreateWeakRef();
    EXPECT_EQ(value, array->Get(1));
#endif
}

TEST_F(WeakRefGenGCTest, DISABLED_DynObjectUndefined)
{
#if !defined(NDEBUG)
    JSHandle<JSObject> new_obj1(thread, JSObjectTestCreate(thread));
    JSTaggedValue array(ArrayTestCreate(thread));
    array.CreateWeakRef();
    new_obj1->SetElements(thread, array);
    EXPECT_EQ(new_obj1->GetElements(), array);
    thread->GetEcmaVM()->GetGC()->WaitForGC(GCTask(GCTaskCause::EXPLICIT_CAUSE));
    EXPECT_EQ(new_obj1->GetElements(), JSTaggedValue::Undefined());
#endif
}

TEST_F(WeakRefGenGCTest, DISABLED_DynObjectKeep)  // issue #5368
{
#if !defined(NDEBUG)
    JSHandle<JSObject> new_obj1(thread, JSObjectTestCreate(thread));
    JSHandle<TaggedArray> array(thread, ArrayTestCreate(thread));
    JSTaggedValue value = array.GetTaggedValue();
    value.CreateWeakRef();
    new_obj1->SetElements(thread, value);
    EXPECT_EQ(new_obj1->GetElements(), value);
    thread->GetEcmaVM()->GetGC()->WaitForGC(GCTask(GCTaskCause::EXPLICIT_CAUSE));
    value = array.GetTaggedValue();
    value.CreateWeakRef();
    EXPECT_EQ(new_obj1->GetElements(), value);
#endif
}
}  // namespace ark::test
